package com.tn.openrpc.stomp
{
	import com.tn.openrpc.TimeoutError;
	import com.tn.openrpc.channel.RemoteChannelRequest;
	import com.tn.openrpc.client.IStompClient;
	import com.tn.openrpc.client.event.StompClientEvent;
	import com.tn.openrpc.util.Base64Util;
	import com.tn.utils.LoggingDispatcher;
	import com.tn.utils.TimeoutInvoker;
	import mx.core.mx_internal;
	import mx.rpc.AsyncToken;
	import mx.rpc.Fault;
	import mx.rpc.events.FaultEvent;
	import mx.rpc.events.ResultEvent;
	import mx.utils.UIDUtil;

	use namespace mx_internal;

	public class StompTemplate extends LoggingDispatcher
	{

		/*============================================================================*/
		/* Private Static Properties                                                  */
		/*============================================================================*/

		private static const HEADER_CORRELATION_ID:String = "correlation-id";

		private static const HEADER_ID:String = "id";

		private static const HEADER_REPLY_TO:String = "reply-to";

		private static const REQUEST_TIMEOUT:Number = (1000 * 10);


		/*============================================================================*/
		/* Public Properties                                                          */
		/*============================================================================*/

		private var _token:AsyncToken;

		public function get token():AsyncToken
		{
			return _token ||= new AsyncToken();
		}

		/*============================================================================*/
		/* Internal Properties                                                        */
		/*============================================================================*/

		private var _headers:Object;

		internal function get headers():Object
		{
			return _headers ||= createHeaders();
		}

		internal var _receiptId:String;

		/**
		 * Creates a receipt-id from the <code>remoteChannelRequest</code>.
		 */
		internal function get receiptId():String
		{
			return _receiptId ||= (request.target + "." + request.methodName);
		}

		internal var _subscriptionHeaders:Object;

		internal function get subscriptionHeaders():Object
		{
			return _subscriptionHeaders ||= createSubscriptionHeaders();
		}

		internal var _transactionId:String;

		internal function get transactionId():String
		{
			return _transactionId ||= UIDUtil.createUID();
		}

		/*============================================================================*/
		/* Private Properties                                                         */
		/*============================================================================*/

		private var client:IStompClient;

		private var correlationId:String;

		private var request:RemoteChannelRequest;

		private var requestDestination:String;

		private var responseDestination:String;

		private var _timeoutInvoker:TimeoutInvoker;

		private function get timeoutInvoker():TimeoutInvoker
		{
			return _timeoutInvoker ||= new TimeoutInvoker(REQUEST_TIMEOUT);
		}

		/*============================================================================*/
		/* Constructor                                                                */
		/*============================================================================*/

		public function StompTemplate(
			client:IStompClient,
			requestDestination:String,
			responseDestination:String,
			correlationId:String)
		{
			this.client = client;
			this.requestDestination = requestDestination;
			this.responseDestination = responseDestination;
			this.correlationId = correlationId;
		}


		/*============================================================================*/
		/* Public Functions                                                           */
		/*============================================================================*/


		public function send(remoteChannelRequest:RemoteChannelRequest):AsyncToken
		{
			timeoutInvoker.start(fault, new TimeoutError(TimeoutError.SEND_REQUEST));

			request = remoteChannelRequest;

			if (request)
			{
				sendRemoteChannelRequest();
				client.addEventListener(StompClientEvent.MESSAGE, clientMessageHandler);
			}
			return token;
		}

		/*============================================================================*/
		/* Protected Functions                                                        */
		/*============================================================================*/

		protected function sendRemoteChannelRequest():void
		{
			client.subscribe(responseDestination, null, receiptId, subscriptionHeaders);
			client.begin(transactionId, receiptId, headers);

			try
			{
				client.send(
					Base64Util.encodeBody(request),
					requestDestination,
					transactionId,
					receiptId,
					headers
					);
			}
			catch (error:Error)
			{
				fault(error)
			}
			finally
			{
				client.commit(transactionId, receiptId, headers);
			}
		}

		/*============================================================================*/
		/* Internal Functions                                                         */
		/*============================================================================*/

		internal function clientMessageHandler(event:StompClientEvent):void
		{
			if (event.headers[HEADER_CORRELATION_ID] == correlationId)
			{
				//Cleanup
				timeoutInvoker.cancel();
				client.removeEventListener(StompClientEvent.MESSAGE, clientMessageHandler);
				client.unsubscribe(responseDestination, receiptId, headers);

				result(event.body);
			}
		}

		internal function fault(info:Object):void
		{
			const error:Error = Error(info);

			token.applyFault(FaultEvent.createEvent(
				new Fault(error.name, error.message, error.message),
				token
				));
		}

		internal function result(data:Object):void
		{
			token.applyResult(ResultEvent.createEvent(
				Base64Util.decodeBody(String(data)),
				token
				));
		}

		/*============================================================================*/
		/* Private Functions                                                          */
		/*============================================================================*/

		private function createHeaders():Object
		{
			var headers:Object = new Object();
			headers[HEADER_CORRELATION_ID] = correlationId;
			headers[HEADER_REPLY_TO] = responseDestination;

			return headers;
		}

		/**
		 * Creates the subscription headers.
		 */
		private function createSubscriptionHeaders():Object
		{
			const subscribeHeaders:Object = new Object();
			subscribeHeaders[HEADER_ID] = UIDUtil.createUID();

			return subscribeHeaders;
		}
	}
}
